#include<iostream>
#include<cstdlib>
#include<ctime>
#include<fstream>
#include<cstring>
using namespace std;

#include "brute_force.h"
#include "simulated_annealing.h"
#include "local_search.h"
#include "grasp.h"
#include "genetic_algorithm.h"
#include "transgenetic_algorithm.h"
#include "particleswarm.h"
#include "kinter_ilp.h"

const char* instances[] = {
	//C1
	"../instances/20_10_100_300_100_300",
	"../instances/40_20_100_300_100_300",
	"../instances/60_30_100_300_100_300",
	"../instances/80_40_100_300_100_300",
	"../instances/100_50_100_300_100_300",
	//C2
	"../instances/20_10_100_300_400_600",
	"../instances/40_20_100_300_400_600",
	"../instances/60_30_100_300_400_600",
	"../instances/80_40_100_300_400_600",
	"../instances/100_50_100_300_400_600",
	//C3
	"../instances/20_10_100_300_700_900",
	"../instances/40_20_100_300_700_900",
	"../instances/60_30_100_300_700_900",
	"../instances/80_40_100_300_700_900",
	"../instances/100_50_100_300_700_900",
	//C4
	"../instances/20_10_400_600_100_300",
	"../instances/40_20_400_600_100_300",
	"../instances/60_30_400_600_100_300",
	"../instances/80_40_400_600_100_300",
	"../instances/100_50_400_600_100_300",
	//C5
	"../instances/20_10_400_600_400_600",
	"../instances/40_20_400_600_400_600",
	"../instances/60_30_400_600_400_600",
	"../instances/80_40_400_600_400_600",
	"../instances/100_50_400_600_400_600",
	//C6
	"../instances/20_10_400_600_700_900",
	"../instances/40_20_400_600_700_900",
	"../instances/60_30_400_600_700_900",
	"../instances/80_40_400_600_700_900",
	"../instances/100_50_400_600_700_900",
	//C7
	"../instances/20_10_700_900_100_300",
	"../instances/40_20_700_900_100_300",
	"../instances/60_30_700_900_100_300",
	"../instances/80_40_700_900_100_300",
	"../instances/100_50_700_900_100_300",
	//C8
	"../instances/20_10_700_900_400_600",
	"../instances/40_20_700_900_400_600",
	"../instances/60_30_700_900_400_600",
	"../instances/80_40_700_900_400_600",
	"../instances/100_50_700_900_400_600",
	//C9
	"../instances/20_10_700_900_700_900",
	"../instances/40_20_700_900_700_900",
	"../instances/60_30_700_900_700_900",
	"../instances/80_40_700_900_700_900",
	"../instances/100_50_700_900_700_900"
};

#define NUMBER_OF_INSTANCES 45
#define REPETITIONS 30
#define EPSON 0.000001
#define Z 1.96

float average( float array[REPETITIONS] ){
	float total_sum = 0;
	for( int i = 0; i < REPETITIONS; ++i )
		total_sum += array[i];
	//total_sum += EPSON;
	
	return ( total_sum / (float)REPETITIONS );
}
 
float standardDeviation( float array[REPETITIONS] ){
	float total_sum = 0;
	float variance = 0;
	float average_array = average( array );
	
	for( int i = 0; i < REPETITIONS; ++i)
		total_sum += pow( ( array[i] - average_array), 2 );
	//total_sum += EPSON;
	
	variance = total_sum / (REPETITIONS-1);
	return sqrt( variance ) ;
	
}

void confidenceInterval( float array[REPETITIONS], float* point_value, float* inferior_limit, float* superior_limit ){
	float avg = average(array);
	float std = standardDeviation(array);
	
	*inferior_limit = avg - Z * ( std / sqrt(REPETITIONS)) + EPSON;
	*superior_limit = avg + Z * ( std / sqrt(REPETITIONS)) + EPSON;
	*point_value = avg;	
}

Algorithm* getAlgorithmInstance( const char* algorithm, const char* instance ){
	if( !strcmp( algorithm, "localsearch" ) )
		return new LocalSearch( instance );
	else if( !strcmp( algorithm, "grasp" ) )
		return new Grasp( instance );
	else if( !strcmp( algorithm, "simulatedannealing" ) )
		return new SimulatedAnnealing( instance );
	else if( !strcmp( algorithm, "geneticalgorithm" ) )
		return new GeneticAlgorithm( instance );
	else if( !strcmp( algorithm, "transgeneticalgorithm" ) )
		return new TransgeneticAlgorithm( instance );
	else if( !strcmp( algorithm, "particleswarm" ) )
		return new ParticleSwarm( instance );
	else if( !strcmp( algorithm, "kinterilp" ) )
		return new KInterILP( instance );
	else
		return NULL;
}

int main( int argc, char** argv ){
	srand(time(NULL));
	char output[50] = "output-";
	ofstream out( strcat(output,argv[1]), fstream::out );
	
	/*
	for( int i = 0; i < NUMBER_OF_INSTANCES; ++i ){
		cout << "Executando instancia " << i << endl;
		KInterILP alg( instances[6] );
		alg.solve();
		
		return -1;
	}
	//*/
	
	if( !strcmp(argv[1], "bruteforce") ){
		for( int i = 0; i < NUMBER_OF_INSTANCES; ++i ){
			BruteForce bf(instances[i]);
			bf.solve();
		}
		return 0;
	}
	else if( !strcmp(argv[1], "kinterilp") ){
		for( int i = 0; i < NUMBER_OF_INSTANCES; ++i ){
			KInterILP ilp(instances[i]);
			ilp.solve();
		}
		return 0;
	}
	else{
		Algorithm* alg;
		float solutions[REPETITIONS];
		for( int i = 0; i < NUMBER_OF_INSTANCES; ++i ){
			
			float best, worst, avg, std_dev;
			best = -99999;
			worst = 99999;
			
			for( int k = 0; k < REPETITIONS; ++k ){
				cout << "Executando instancia " << i+1 << ", repeticao  " << k+1 << endl;
				Algorithm* alg = getAlgorithmInstance( argv[1], instances[i] );
				
				if(alg == NULL){
					return -1;
				}
				
				alg->solve();
				
				solutions[k] = alg->getQualityOfBest();
				delete alg;
			}
			
			for( int k = 0; k < REPETITIONS; ++k ){
				if(solutions[k] > best)
					best = solutions[k];
				if(solutions[k] < worst)
					worst = solutions[k];
			}
			
			avg = average(solutions);
			std_dev = standardDeviation(solutions);
			
			out << i+1 << " " << avg << " " << best << " " << worst << " " << std_dev << endl;
		}
	}
	out.close();
}
